home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Interactive 7
/
PC World Interactive 7.iso
/
program
/
ctutor.exe
/
TEXT
/
CHAP06.TXT
< prev
next >
Wrap
Text File
|
1994-05-15
|
18KB
|
406 lines
Chapter 6
THE C PREPROCESSOR
AIDS TO CLEAR PROGRAMMING
-----------------------------------------------------------------
The preprocessor is a program that is executed just prior to the
execution of the compiler. It's operation is transparent to you
but it does a very important job. It removes all comments from
the source and performs a lot of textual substitution based on
your code, passing the result to the compiler for the actual
compilation of your code.
Load and display the file named DEFINE.C for ================
your first look at some defines and macros. DEFINE.C
Notice lines 4 through 7 of the program, each ================
starting with #define. This is the way all
defines and macros are defined. Before the actual compilation
starts, the compiler goes through a preprocessor pass to resolve
all of the defines. In the present case, it will find every
place in the program where the combination START is found and it
will replace it with the 0 since that is the definition. The
compiler itself will never see the word START, so as far as the
compiler is concerned, the zeros were always there. Note that if
the string is found in a string constant or in a comment, it will
not be changed.
It should be clear to you that putting the word START in your
program instead of the numeral 0 is only a convenience to you and
actually acts like a comment since the word START helps you to
understand what the zero is used for.
In the case of a very small program, such as that before you, it
doesn't really matter what you use. If, however, you had a 2000
line program before you with 27 references to START, it would be
a completely different matter. If you wanted to change all of
the STARTs in the program to a new number, it would be simple to
change the one #define statement to the new value. If this
technique were not used, it would be difficult to find and
change all of the references to it manually, and possibly
disastrous if you missed one or two of the references.
In the same manner, the preprocessor will find all occurrences of
the word ENDING and change them to 9, then the compiler will
operate on the changed file with no knowledge that ENDING ever
existed.
It is a fairly common practice in C programming to use all
capital letters for a symbolic constant such as START and ENDING
and use all lower case letters for variable names. You can use
any method you choose since it is mostly a matter of personal
taste.
Page 6-1
Chapter 6 - The C Preprocessor
IS THIS REALLY USEFUL?
-----------------------------------------------------------------
When we get to the chapters discussing input and output, we will
need an indicator to tell us when we reach the end-of-file of an
input file. Since different compilers use different numerical
values for this, although most use a minus 1, we will write the
program with a #define to define the EOF used by our particular
compiler. If, at some later date, we change to a new compiler,
it is a simple matter to change this one #define to fix the
entire program. In essentially all C compilers, the EOF is
defined in the STDIO.H file. You can observe this for yourself
by examining the contents of the STDIO.H file that was supplied
with your compiler.
WHAT IS A MACRO?
-----------------------------------------------------------------
A macro is nothing more than another define, but since it is
capable of at least appearing to perform some logical decisions
or some math functions, it has a unique name. Consider line 6 of
the program on your screen for an example of a macro. In this
case, anytime the preprocessor finds the word MAX followed by a
group in parentheses, it expects to find two terms in the
parentheses and will do a replacement of the terms into the
second part of the definition. Thus the first term will replace
every A in the second part of the definition and the second term
will replace every B in the second part of the definition. When
line 15 of the program is reached, index will be substituted for
every A, and count will be substituted for every B. Once again,
it must be stated that string constants and comments will not be
affected. Remembering the cryptic construct we studied a couple
of chapters ago will reveal that mx will receive the maximum
value of index or count. In like manner, the MIN macro will
result in mn receiving the minimum value of index or count.
These two particular macros are very common in C programs.
When defining a macro, it is imperative that there is no space
between the macro name and the opening parenthesis. If there is
a space, the compiler cannot determine that it is a macro, but
will handle it like a simple substitution define statement.
The results of the macro usage are then printed out in line 17.
There are a lot of seemingly extra parentheses in the macro
definition but they are not extra, they are essential. We will
discuss the extra parentheses in our next example program. Be
sure to compile and execute DEFINE.C before going on to the next
example program.
Page 6-2
Chapter 6 - The C Preprocessor
LET'S LOOK AT A WRONG MACRO
-----------------------------------------------------------------
Load the file named MACRO.C and display it on =================
your screen for a better look at a macro and MACRO.C
its use. Line 4 defines a macro named WRONG =================
that appears to evaluate the cube of A, and
indeed it does in some cases, but it fails miserably in others.
The second macro named CUBE actually does get the cube in all
cases.
Consider the program itself where the CUBE of i+offset is
calculated. If i is 1, which it is the first time through, then
we will be looking for the cube of 1+5 = 6, which will result in
216. When using CUBE, we group the values like this,
(1+5)*(1+5)*(1+5) = 6*6*6 = 216. However, when we use WRONG, we
group them as 1+5*1+5*1+5 = 1+5+5+5 = 16 which is a wrong answer.
The parentheses are therefore required to properly group the
variables together. It should be clear to you that either CUBE
or WRONG would arrive at a correct answer for a single term
replacement such as we did in the last program. The correct
values of the cube and the square of the numbers are printed out
as well as the wrong values for your inspection.
In line 7 we define the macro ADD_WRONG according to the above
rules but we still have a problem when we try to use the macro in
lines 25 and 26. In line 26 when we say we want the program to
calculate 5*ADD_WRONG(i) with i = 1, we get the result 5*1 + 1
which evaluates to 5 + 1 or 6, and this is most assuredly not
what we had in mind. We really wanted the result to be 5*(1 + 1)
= 5*2 = 10 which is the answer we get when we use the macro named
ADD_RIGHT, because of the extra parentheses around the entire
expression in the definition given in line 8. A little time
spent studying the program and the result will be worth your
effort in understanding how to use macros.
In order to prevent the above problems, most experienced C
programmers include parentheses around each variable in a macro
and additional parentheses around the entire expression. This
will allow any macro to work correctly.
The remainder of the program is simple and will be left to your
inspection and understanding.
CONDITIONAL COMPILATION - PART 1
-----------------------------------------------------------------
The example program named IFDEF.C is our first ===============
illustration of a conditional compilation. IFDEF.C
OPTION_1 is defined in line 4, and is ===============
considered defined for the entire program.
Therefore when the preprocessor gets to line 6, it keeps the text
between lines 6 and 8 in the program and passes it to the
compiler. If OPTION_1 was not defined when we reach line 6, the
Page 6-3
Chapter 6 - The C Preprocessor
preprocessor would throw away line 7 and the compiler would never
see it. Likewise line 17 is conditionally compiled based on
whether OPTION_1 is defined or not. This is a very useful
construct, but not the way we are using it here. Generally it is
used to include a feature if we are using a certain processor, a
certain operating system, or even a special piece of hardware.
You should compile and execute the program as is, then comment
out line 4 so that OPTION_1 will not be defined, and recompile
and execute the program. You will see that the extra line will
not be printed because it will be thrown away by the
preprocessor. Keep in mind that the preprocessor does only
textual substitution or text removal and you will be able to use
it effectively.
Line 23 illustrates an undefine command to the preprocessor.
This removes the fact that OPTION_1 was defined and from this
point on, the program acts as though it were never defined. Of
course, it does no good here since the program is completed and
there are no executable statements following the undefine, but it
does illustrate the undefine statement.
You should move the undefine to line 5, recompile and execute the
program, and you will see that it acts as though OPTION_1 was
never defined.
CONDITIONAL COMPILATION - PART 2
-----------------------------------------------------------------
The next example program illustrates the ================
preprocessor directive which includes code if IFNDEF.C
a symbol in not defined. The ifndef directive ================
reads literally, "if not defined", and with
that much definition, its operation should be intuitive. This
program will be a real exercise in logic for the diligent student,
but should be understandable with a little effort. The symbol
OPTION_1 is reversed from the last program and the symbol
PRINT_DATA is used to enable printing if it is not defined. If
it is not defined, there will be some printout. This example
program, much like the last one, is rather silly but illustrates
the use of preprocessor directives. The next program is a little
more practical.
CONDITIONAL COMPILATION - PART 3
-----------------------------------------------------------------
The program named DEBUGEX.C is a good =================
illustration of a very practical use of the DEBUGEX.C
preprocessor. In this program we define a =================
symbol named MY_DEBUG at the beginning of the
program. When we reach the code in the main program we see why
it is defined. Apparently we do not have enough information to
complete this code, so we sort of slopped it in until we have a
Page 6-4
Chapter 6 - The C Preprocessor
chance to talk to Bill and Linda about how to do these
calculations. In the meantime, we wish to continue work on other
parts of the program, so we use the preprocessor to temporarily
throw away this uncompilable code for us. Because of the
obnoxious message we put into line 14, it will be impossible for
us to forget about the bad state of affairs we left the code in,
so we are forced to come back later and clean it up.
In this case, we are only concerned with a few lines of code, but
it could be a large block of code we are working with. We could
also be using this technique to handle several large blocks of
code, some of which are in other modules, until Bill returns to
explain the analysis and we can complete the undefined blocks.
MULTIPLE FILE PROGRAMS
-----------------------------------------------------------------
For very small programs, it is expedient to include all of the
code in a single file, and compile that one file for the final
resulting code. It is not generally acceptable to do this
because all but the most trivial programs are too big to place
in a single file because the file gets to be very cumbersome to
work with. It is not at all unusual for a C program to be made
up of over a thousand source files. It is, of course, necessary
for these files to communicate and work together as one large
program.
Even though it is best not to use global variables, a variable
that is defined outside of any function, it is sometimes
expedient to use a few. Sometimes these variables need to be
referenced by two or more different files, and C provides a way
to do this. Consider the following three file portions.
FILE1.C FILE2.C FILE3.C
int index; extern int index; extern int index;
extern int count; int count;
static int value; int value;
int main();
static void one(); void two(); void three();
The variable named index declared in FILE1.C is available to any
other file for use because it is declared globally. The other
two files make use of the same variable by declaring it as an
extern variable. In essence, they are telling the compiler, "I
wish to use the variable named index which is defined somewhere
else". Anytime index is referred to in either of the other two
files, the variable of that name is used from FILE1.C, and it can
be read, or modified by any of the three files. This provides an
easy way to pass data from any file to any other file, but it
could lead to problems. It would be very easy for any of these
files to modify index in some way not meant to and corrupt the
data. It could be very difficult to determine which file
corrupted the value of index.
Page 6-5
Chapter 6 - The C Preprocessor
The variable named count is defined in FILE2.C and referred to in
the same manner defined above within FILE1.C, but is not
available for use in FILE3.C because it is not declared in it. A
static variable, such as value in FILE2.C cannot be referenced in
any other file but is hidden in the declaring file by definition.
A completely separate variable named value is declared in FILE3.C
that has nothing to do with the same named variable in FILE2.C.
In this case, FILE1.C could declare value as an external variable
and refer to that variable in FILE3.C if desired.
The main() entry point can only be called by the operating system
to get the program started, but the functions two() and three()
can be called from anywhere within the three files because they
are global functions. The function one() however, because it is
declared static, can only be called from within the file in which
it is declared. It cannot be called from within FILE2.C or
FILE3.C. It is sometimes expedient to "hide" a function within a
file, and it is often referred to as a local function as opposed
to being a global function.
WHAT IS AN ENUMERATION VARIABLE?
-----------------------------------------------------------------
Load and display the program named ENUM.C for ================
an example of how to use the enum type ENUM.C
variable. Line 6 contains the first enum type ================
variable named result which is a variable that
can take on any of the values contained within the braces.
Actually the variable result is an int type variable but can be
assigned any of the values defined for an int type variable. The
names within the parentheses are int type constants and can be
used anywhere it is legal to use an int type constant. The
constant WIN is assigned the value of 0, TIE the value 1, BYE the
value 2, etc.
In use, the variable named result is used just like any int
variable would be used as can be seen by its use in the program.
The enum type of variable is intended to be used by you, the
programmer, as a coding aid since you can use a constant named
MON for control structures rather than the meaningless (at least
to you) value of 1. Notice that days is assigned the values of
days of the week in the remainder of the program. If you were to
use a switch statement, it would be much more meaningful to use
the labels SUN, MON, etc, rather than the more awkward 0, 1, 2,
etc.
All caps are used for the enumeration values in this program as a
matter of personal taste because they are all constants. There
is no universal standard on this matter and each programmer is
free to do as he wishes. All caps for these values tends to be
standard practice however.
Page 6-6
Chapter 6 - The C Preprocessor
WHAT IS A PRAGMA?
-----------------------------------------------------------------
A pragma is an instruction to your compiler to perform some
particular action at compile time. Although pragmas vary from
compiler to compiler and are not standardized, they perform some
useful functions. Your compiler probably supports some way for
you to select the optimization method by inserting a pragma into
the source code. If your compiler provides a source listing
file, you probably have pragmas to format the output listing to
your personal preference. Check your documentation for the
pragmas that are provided by your compiler.
PROGRAMMING EXERCISE
-----------------------------------------------------------------
1. Write a program to count from 7 to -5 by counting down. Use
#define statements to define the limits. (Hint, you will need
to use a decrementing variable in the third part of the for
loop control.
Page 6-7